/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/*************************************************************************
* File: pool.hpp
*************************************************************************
*\author: starlove
*************************************************************************/

#ifndef __CC_PYTHON_POOL_H__
#define __CC_PYTHON_POOL_H__

#include "platform.hpp"
#include "singleton.hpp"
#include "bits.hpp"
#include "time.hpp"
#include <iostream>
#include <python_import_macro.h>

NS_CCPY_BEGIN

#define CHUNK_FLAG_FREE 0
#define CHUNK_FLAG_USED 1

#define CHUNK_CHECKSUM 0xE38E38E

#define POP_CHUNK(b, first)\
	do{\
	if(b->next) b->next->ppre = NULL;\
	first = b->next;\
	b->flag = CHUNK_FLAG_USED;\
	}while(0);

#define INSERT_CHUNK(b, first)\
	do{\
	b->next = first;\
	if(b->next) b->next->ppre = &(b->next);\
	first = b;\
	b->ppre = NULL;\
	b->flag = CHUNK_FLAG_FREE;\
	}while(0);

#define GET_CHUNK_FROM_AVAILABLE_ADDR(p) ((pchunk)(((char *)p) - sizeof(chunk)))
#define GET_CHUNK_AVAILABLE_ADDR(ck) (((char *)ck) + sizeof(chunk))
#define GET_CHUNK_AVAILABLE_SIZE(ck) ( (2<<((int)ck->index)) )
#define GET_EXCHUNK_AVAILABLE_SIZE(ck) ( ((int)ck->index) )
#define GET_CHUNK_SIZE(index) ((2<<((int)index)) + sizeof(chunk))
#define GET_EXCHUNK_SIZE(size) (size + sizeof(chunk))

	class memorypool
	{
	public:
#pragma pack(1)
		// chunk|memory|chunk|memory...
		// memory's ddr = chunk's addr + sizeof(chunk)
		typedef struct __chunk
		{
			unsigned flag 		: 1;
			unsigned checksum 	: 31;
			unsigned index 		: 32;// chunk's index, if extend, index is the chunk real-size
			__chunk *next;		// next chunk
			__chunk **ppre;		// `next`'s ptr for prev-chunk
		}chunk, *pchunk;

		typedef struct __pool
		{
			int si;				// chunks index start
			int ei;				// chunks index end
			unsigned int min;	// min chunk size
			unsigned int max;	// max chunk size
			unsigned int checksum;
			pchunk chunks[32];
			pchunk extended;
		} pool, *ppool;
#pragma pack()

	private:
		static int find_chunk_index(ppool p, unsigned int size)
		{
			int n = p->si;
			while((2 << n) < (int)size && n <= p->ei)
			{
				n ++;
			}
			return n <= p->ei ? n : -1;
		};

		static void *alloc_from_system(ppool p, unsigned int size)
		{
			pchunk ck = p->chunks[0];
			while(ck && ck->index < size)
			{
				ck = ck->next;
			}

			if(!ck)
			{
				ck = reinterpret_cast<pchunk>(::malloc(GET_EXCHUNK_SIZE(size)));
				if(!ck)	return ck;
				ck->checksum = p->checksum;
				ck->flag = CHUNK_FLAG_FREE;
				ck->index = size;
			}else
				POP_CHUNK(ck, p->chunks[0]);
			return GET_CHUNK_AVAILABLE_ADDR(ck);
		}

		static pchunk create_chunk(ppool p, int index)
		{
			void *ptr = ::malloc(GET_CHUNK_SIZE(index));
			if(!ptr)
				return NULL;
			pchunk ck = reinterpret_cast<pchunk>(ptr);
			ck->checksum = p->checksum;
			ck->flag = CHUNK_FLAG_USED;
			ck->index = index;
			//printf("create chunk, checksum: %d, chunk addr: %p\n", ck->checksum, ck);
			return ck;
		}

		static void *pop_available_chunk(ppool p, int index, unsigned int size)
		{
			pchunk ck = p->chunks[index];
			if(ck)
			{
				POP_CHUNK(ck, p->chunks[index]);
				//printf("pop chunk\n");
			}else
			{
				ck = create_chunk(p, index);
				if(!ck)
					return NULL;
			}
			return GET_CHUNK_AVAILABLE_ADDR(ck);
		}

	public:
		static ppool create(unsigned int min = 0x10, unsigned int max = 0x80000000)
		{
			int si = CBitUtils::GetInstance()->sqrt_for_2(min);
			int ei = CBitUtils::GetInstance()->sqrt_for_2(max);
			if(si < 0 || ei < 0)
				return NULL;
			ppool ptr = reinterpret_cast<ppool>(::malloc(sizeof(pool)));
			::memset(ptr, 0, sizeof(pool));
			ptr->min 	= min;
			ptr->max 	= max;
			ptr->si		= si;
			ptr->ei		= ei;
			ptr->checksum = CTimeUtils::GetTimestamp() % 0x7FFFFFFF;
			return ptr;
		};

		static void destroy(ppool p)
		{
			if(p)	::free(p);
		};

		static void *alloc(ppool p, unsigned int size)
		{
			int index = find_chunk_index(p, size);
			if(index < p->si || index > p->ei)
				return alloc_from_system(p, size);

			void *ptr = pop_available_chunk(p, index, size);
			if(!ptr)
				return alloc_from_system(p, size);

			//printf("alloc addr: %p\n", ptr);
			return ptr;
		};

		static void release(ppool p, void *ptr)
		{
			if(!ptr)
				return;
			pchunk ck = GET_CHUNK_FROM_AVAILABLE_ADDR(ptr);
			if(ck->checksum != p->checksum)
			{
				//no pool memory free!!!!
				//DEBUG_INFO_LIBRARY("no pool memory free!!!! checksum: %d, ptr's checksum: %d, ptr: %p, chunk addr: %p", CHUNK_CHECKSUM, ck->checksum, ptr, ck);
				return;
			}
			ck->flag = CHUNK_FLAG_FREE;
			int t_index = ck->index;
			if(t_index < p->si || t_index > p->ei)	t_index = 0;
			INSERT_CHUNK(ck, p->chunks[t_index]);
		}

		static void *expand(ppool p, void *data, unsigned int src_size, unsigned int size)
		{
			int index = find_chunk_index(p, size);
			pchunk ck = GET_CHUNK_FROM_AVAILABLE_ADDR(data);
			if(ck->index == (unsigned int)index)
				return data;

			void *ptr = (index < p->si || index > p->ei) ? alloc_from_system(p, size) : pop_available_chunk(p, index, size);
			if(!ptr)
				return alloc_from_system(p, size);
			memcpy(ptr, data, src_size);
			release(p, data);
			return ptr;
		}

		static void clear(ppool p)
		{
			for(int i = 0; i < 32; i ++)
			{
				pchunk ck = p->chunks[i];
				if(!ck)	continue;
				while(ck)
				{
					pchunk _ck = ck;
					ck = ck->next;
					POP_CHUNK(_ck, p->chunks[i]);
					::free(_ck);
				}
			}
		}

		static void print_memory_table(ppool p)
		{
			std::cout << ">>>>>>>>>>>>>>>>>>> memory_table_begin >>>>>>>>>>>>>>>>>\n\n";
			std::cout << ">>>>>>>>> memory_table_standard >>>>>>>>\n";
			for(int i = p->si; i <= p->ei; i ++)
			{
				pchunk ck = p->chunks[i];
				if(!ck)	continue;
				std::cout << ">>> chunk linkedlist ";
				std::cout << i;
				std::cout << "\n";
				int c = 0;
				while(ck)
				{
					std::cout << ">>>>>> chunk: ";
					std::cout << reinterpret_cast<unsigned long>(GET_CHUNK_AVAILABLE_ADDR(ck));
					std::cout << " size: ";
					std::cout << GET_CHUNK_AVAILABLE_SIZE(ck);
					std::cout << "\n";
					ck = ck->next;
					c ++;
				}
				std::cout << ">>> chunk count: ";
				std::cout << c;
				std::cout << "\n";
			}
			std::cout << ">>>>>>>>> memory_table_extend >>>>>>>>\n";
			pchunk ck = p->chunks[0];
			int c = 0;
			while(ck)
			{
				std::cout << ">>>>>> chunk: ";
				std::cout << GET_CHUNK_AVAILABLE_ADDR(ck);
				std::cout << " size: ";
				std::cout << GET_CHUNK_AVAILABLE_SIZE(ck);
				std::cout << "\n";
				ck = ck->next;
				c ++;
			}
			std::cout << ">>> chunk count: ";
			std::cout << c;
			std::cout << "\n";
			std::cout << ">>>>>>>>>>>>>>>>>>> memory_table_end >>>>>>>>>>>>>>>>>\n\n";
		}
	};

	class CSimplePool
	{
		DECLARE_SINGLETON(CSimplePool)
	public:
		void Initialize()
		{
			m_pool = memorypool::create();
		}

		void Destroy()
		{
			memorypool::destroy(m_pool);
		}

		void *Alloc(size_t s)
		{
			return memorypool::alloc(m_pool, s);
		}

		void Free(void *p)
		{
			memorypool::release(m_pool, p);
		}

		void *Swap(void *p, size_t src_size, size_t new_size)
		{
			return memorypool::expand(m_pool, p, src_size, new_size);
		}

	private:
		memorypool::ppool m_pool;
	};

NS_CCPY_END

#endif // !__CC_PYTHON_POOL_H__

